/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ecmascript/tests/test_helper.h"

#include "common_components/heap/w_collector/w_collector.h"
#include "common_components/heap/collector/collector_proxy.h"
#include "common_components/heap/heap_manager.h"
#include "common_components/heap/allocator/region_desc.h"
#include "common_components/base_runtime/base_runtime_param.h"
#include "ecmascript/mem/dynamic_object_operator.h"
#include "ecmascript/js_hclass.h"
#include "ecmascript/mem/tagged_object.h"

using namespace common;

namespace panda::test {
class WCollectorTest : public BaseTestWithScope<false> {};

std::unique_ptr<WCollector> GetWCollector()
{
    CollectorResources& resources = Heap::GetHeap().GetCollectorResources();
    Allocator& allocator = Heap::GetHeap().GetAllocator();
    
    return std::make_unique<WCollector>(allocator, resources);
}

HWTEST_F_L0(WCollectorTest, ForwardUpdateRawRefTest1) {
    std::unique_ptr<WCollector> wcollector = GetWCollector();
    ASSERT_TRUE(wcollector != nullptr);

    size_t allocSize = sizeof(BaseObject) + sizeof(BaseObject) + sizeof(BaseClass);
    HeapAddress addr = HeapManager::Allocate(allocSize, AllocType::MOVEABLE_OBJECT, true);
    BaseObject *obj = reinterpret_cast<BaseObject*>(addr);
    new (obj) BaseObject();

    size_t addrOffset = sizeof(BaseObject);
    BaseObject *objFwd = reinterpret_cast<BaseObject*>(addr + addrOffset);
    new (objFwd) BaseObject();

    addrOffset += sizeof(BaseObject);
    objFwd->SetFullBaseClassWithoutBarrier(reinterpret_cast<BaseClass *>(addr + addrOffset));

    common::ObjectRef root = {obj};

    RefField<false> field(obj);
    field.SetTargetObject(obj);
    EXPECT_EQ(field.GetTargetObject(), obj);

    auto regionInfo = RegionDesc::GetRegionDescAt(reinterpret_cast<uintptr_t>(obj));
    regionInfo->SetRegionType(RegionDesc::RegionType::FROM_REGION);

    Mutator::GetMutator()->SetMutatorPhase(GCPhase::GC_PHASE_COPY);

    obj->SetForwardState(BaseStateWord::ForwardState::FORWARDED);
    ecmascript::DynamicObjectOperator dynamicObjOp;
    obj->RegisterDynamic(reinterpret_cast<BaseObjectOperatorInterfaces *>(&dynamicObjOp));
    dynamicObjOp.SetForwardingPointerAfterExclusive(obj, objFwd);

    auto taggedObject = ecmascript::TaggedObject::Cast(objFwd);
    ecmascript::JSHClass *jsHclass = taggedObject->GetClass();
    EXPECT_EQ(reinterpret_cast<HeapAddress>(jsHclass), addr + addrOffset);

    jsHclass->SetObjectType(ecmascript::JSType::HCLASS);

    BaseObject *oldObj = wcollector->ForwardUpdateRawRef(root);
    EXPECT_NE(oldObj, obj);
    EXPECT_EQ(oldObj, objFwd);

    regionInfo->SetRegionType(RegionDesc::RegionType::THREAD_LOCAL_REGION);
}

HWTEST_F_L0(WCollectorTest, FixRefFieldTest0) {
    std::unique_ptr<WCollector> wcollector = GetWCollector();
    ASSERT_TRUE(wcollector != nullptr);

    BaseObject *obj = nullptr;

    RefField<false> field(obj);

    field.SetTargetObject(obj);
    EXPECT_EQ(field.GetTargetObject(), obj);

    wcollector->FixRefField(obj, field);
}

HWTEST_F_L0(WCollectorTest, FixRefFieldTest1) {
    std::unique_ptr<WCollector> wcollector = GetWCollector();
    ASSERT_TRUE(wcollector != nullptr);

    size_t allocSize = sizeof(BaseObject) + sizeof(BaseObject) + sizeof(BaseClass) + sizeof(BaseClass);
    HeapAddress addr = HeapManager::Allocate(allocSize, AllocType::MOVEABLE_OBJECT, true);
    BaseObject *obj = reinterpret_cast<BaseObject*>(addr);
    new (obj) BaseObject();

    size_t addrOffset = sizeof(BaseObject);
    BaseObject *objFwd = reinterpret_cast<BaseObject*>(addr + addrOffset);
    new (objFwd) BaseObject();

    addrOffset += sizeof(BaseObject);
    objFwd->SetFullBaseClassWithoutBarrier(reinterpret_cast<BaseClass *>(addr + addrOffset));

    obj->SetForwardState(BaseStateWord::ForwardState::FORWARDED);
    ecmascript::DynamicObjectOperator dynamicObjOp;
    obj->RegisterDynamic(reinterpret_cast<BaseObjectOperatorInterfaces *>(&dynamicObjOp));
    dynamicObjOp.SetForwardingPointerAfterExclusive(obj, objFwd);

    auto taggedObject = ecmascript::TaggedObject::Cast(objFwd);
    ecmascript::JSHClass *jsHclass = taggedObject->GetClass();
    EXPECT_EQ(reinterpret_cast<HeapAddress>(jsHclass), addr + addrOffset);
    jsHclass->SetObjectType(ecmascript::JSType::HCLASS);

    addrOffset += sizeof(BaseClass);
    jsHclass->SetFullBaseClassWithoutBarrier(reinterpret_cast<BaseClass *>(addr + addrOffset));
    jsHclass->GetClass()->SetObjectType(ecmascript::JSType::HCLASS);

    RefField<false> field(obj);
    field.SetTargetObject(obj);
    EXPECT_EQ(field.GetTargetObject(), obj);

    wcollector->FixRefField(obj, field);
}

class WCollectorMockTest : public BaseTestWithScope<false> {
public:
    void SetUp() override
    {
        BaseRuntime::GetInstance()->Init();
        MutatorManager::Instance().CreateRuntimeMutator(ThreadType::GC_THREAD);
    }

    void TearDown() override
    {
        MutatorManager::Instance().DestroyRuntimeMutator(ThreadType::GC_THREAD);
        ThreadLocal::SetThreadType(ThreadType::ARK_PROCESSOR);

        BaseRuntime::GetInstance()->Fini();
        BaseRuntime::GetInstance()->DestroyInstance();
        ecmascript::TaggedStateWord::BASE_ADDRESS = 0;
    }
};

class WCollectorMock : public WCollector {
public:
    explicit WCollectorMock(Allocator& allocator, CollectorResources& resources)
        : WCollector(allocator, resources) {}

    ~WCollectorMock() override = default;

    bool TryUpdateRefFieldMock(BaseObject* obj, RefField<>& field, BaseObject*& newRef)
    {
        return this->TryUpdateRefField(obj, field, newRef);
    }

    bool TryForwardRefFieldMock(BaseObject* obj, RefField<>& field, BaseObject*& newRef)
    {
        return this->TryForwardRefField(obj, field, newRef);
    }

    void DoGarbageCollectionMock()
    {
        this->DoGarbageCollection();
    }
};

std::unique_ptr<WCollectorMock> GetWCollectorMock()
{
    CollectorResources& resources = Heap::GetHeap().GetCollectorResources();
    Allocator& allocator = Heap::GetHeap().GetAllocator();
    
    return std::make_unique<WCollectorMock>(allocator, resources);
}

HWTEST_F_L0(WCollectorMockTest, TraceRefFieldTest0) {
    ThreadLocal::SetThreadType(ThreadType::ARK_PROCESSOR);
    std::unique_ptr<WCollector> wcollector = GetWCollector();
    ASSERT_TRUE(wcollector != nullptr);

    Mutator::GetMutator()->SetMutatorPhase(GCPhase::GC_PHASE_ENUM);

    size_t allocSize = sizeof(BaseObject) + sizeof(BaseObject) + sizeof(BaseClass);
    HeapAddress addr = HeapManager::Allocate(allocSize, AllocType::MOVEABLE_OBJECT, true);
    BaseObject *obj = reinterpret_cast<BaseObject*>(addr);
    new (obj) BaseObject();

    size_t addrOffset = sizeof(BaseObject);
    BaseObject *objFwd = reinterpret_cast<BaseObject*>(addr + addrOffset);
    new (objFwd) BaseObject();

    addrOffset += sizeof(BaseObject);
    objFwd->SetFullBaseClassWithoutBarrier(reinterpret_cast<BaseClass *>(addr + addrOffset));

    obj->SetForwardState(BaseStateWord::ForwardState::FORWARDED);
    ecmascript::DynamicObjectOperator dynamicObjOp;
    obj->RegisterDynamic(reinterpret_cast<BaseObjectOperatorInterfaces *>(&dynamicObjOp));
    dynamicObjOp.SetForwardingPointerAfterExclusive(obj, objFwd);

    auto taggedObject = ecmascript::TaggedObject::Cast(objFwd);
    ecmascript::JSHClass *jsHclass = taggedObject->GetClass();
    EXPECT_EQ(reinterpret_cast<HeapAddress>(jsHclass), addr + addrOffset);
    jsHclass->SetObjectType(ecmascript::JSType::HCLASS);

    RefField<false> field(obj);
    field.SetTargetObject(obj);
    EXPECT_EQ(field.GetTargetObject(), obj);
    MarkStackBuffer<BaseObject*> workBuffer;
    MarkStackBuffer<RefField<>*> weakBuffer;
    workBuffer.push_back(obj);
    weakBuffer.push_back(&field);
    
    TraceCollector::WorkStack workStack;
    TraceCollector::WeakStack weakStack;
    wcollector->TraceRefField(obj, field, workStack, weakStack);

    ThreadLocal::SetThreadType(ThreadType::GC_THREAD);
}

} // namespace panda::test